import { Helper } from "./helper";
import { types } from "./types";

const BookENG = [0b11101, 0b101010111, 0b10111010111, 0b1010111, 0b1, 0b101110101, 0b101110111, 0b1010101, 0b101, 0b1110111011101, 0b111010111, 0b101011101, 0b1110111, 0b10111, 0b11101110111, 0b10111011101, 0b1110101110111, 0b1011101, 0b10101, 0b111, 0b1110101, 0b111010101, 0b111011101, 0b11101010111, 0b1110111010111, 0b10101110111];
const BookNUM = [0b1110111011101110111, 0b11101110111011101, 0b111011101110101, 0b1110111010101, 0b11101010101, 0b101010101, 0b10101010111, 0b1010101110111, 0b101011101110111, 0b10111011101110111];
const BookFH = [0b11101011101011101, 0b10101011101110111, 0b1110111010101110111, 0b10111010111010111, 0b101011101110101, 0b1110101010111, 0b1011101110111011101, 0b1011101010111, 0b1110111010111010111, 0b111010101010111, 0b11101011101110101, 0b101110101011101, 0b1110101110111010111, 0b11101010111010101, 0b10111010111011101];
const BookSPEC = [0b111010111011101, 0b11101011101, 0b111011101110111, 0b1010111010111, 0b1011101110101, 0b10101110101, 0b1110101011101, 0b101110101110111, 0b101110111010111, 0b101110111011101, 0b11101110101110111, 0b1011101110111, 0b10111010101, 0b1010111011101, 0b11101110101];
const AR = 0b1011101011101;
const AS = 0b10101011101;
const SK = 0b111010111010101;
const BT = 0b1110101010111;
const FH = ".:,;?='/!-_\"($@";
const SPEC = "åæ çðéèĝĥĵñöŝþü";
class Morse_bag { public text: string = ""; }
class StandardMorseBook {
  public constructor() {
    this.kuohao_times = true;
  }
  public kuohao_times: boolean;
  public Encode(c: string): number {

    var code = c.charCodeAt(0);
    if (code >= 65 && code <= 90) {
      return BookENG[code - 65];
    }
    else if (code >= 97 && code <= 122) {
      return BookENG[code - 97];
    }
    else if (code >= 48 && code <= 57) {
      return BookNUM[code - 48];
    }
    var i = FH.indexOf(c);
    if (i != -1) return BookFH[i];
    i = SPEC.indexOf(c);
    if (i != -1) return BookSPEC[i];
    switch (c) {
      case ')': return BookFH[12];
      case 'à': return BookSPEC[0];
      case 'ä': return BookSPEC[1];
      case 'ĉ': return BookSPEC[3];
      case 'ø': return BookSPEC[11];
      case 'ŭ': return BookSPEC[14];
    }
    return 0;
  }
  public Encode2(c1: string, c2: string): number {

    if (c1 == 'c' && c2 == 'h') return BookSPEC[2];
    else if (c1 == 'A' && c2 == 'R') return AR;
    else if (c1 == 'A' && c2 == 'S') return AS;
    else if (c1 == 'S' && c2 == 'K') return SK;
    else if (c1 == 'B' && c2 == 'T') return BT;
    return 0;
  }
  public Decode(morse1: number, bag: Morse_bag) {

    let morse = morse1;
    let i = 0;
    for (; i < 26; i++) {
      if (BookENG[i] == morse) {
        bag.text += String.fromCharCode(i + 97);
        return;
      }
    }
    for (i = 0; i < 10; i++) {
      if (BookNUM[i] == morse) {
        bag.text += String.fromCharCode(i + 48);
        return;
      }
    }
    for (i = 0; i < 15; i++) {
      if (BookFH[i] == morse) {
        if (i == 12) {
          this.kuohao_times = !this.kuohao_times;
          if (this.kuohao_times) {
            bag.text += ")";
            return;
          }
        }
        bag.text += FH[i];
        return;
      }
    }
    for (i = 0; i < 15; i++) {
      if (BookSPEC[i] == morse) {
        if (i == 2) {
          bag.text += "ch";
          return;
        }
        bag.text += SPEC[i];
        return;
      }
    }
    switch (morse) {
      case AR: bag.text += "AR"; break;
      case AS: bag.text += "AS"; break;
      case SK: bag.text += "SK"; break;
      case BT: bag.text += "BT"; break;
    }
  }
}

export class Morse {
  public constructor() {
    this.book = new StandardMorseBook();
  }
  public book: StandardMorseBook;
  public Encode(text: string, length: number): number[] {

    if (length == 0) return null;
    let bytes = types.number_array(length * 3);
    let x;
    let total = 0;
    function push(n: number, count: number = 0) {
      if (count > 0) {
        total += count;
      }
      else {
        while (n != 0) {
          x = total >> 3;
          n >>= 1;
          if ((n & 0b1) == 0) {
            bytes[x] |= (1 << ((7 - total % 8) & 0xff)) & 0xff;
            total += 1;
            n >>= 1;
          }
          else {
            if (total % 8 == 6) {
              bytes[x] |= 0b11;
              bytes[x + 1] |= 0b10000000;
            }
            else if (total % 8 == 7) {
              bytes[x] |= 1;
              bytes[x + 1] |= 0b11000000;
            }
            else {
              bytes[x] |= (0b111 << ((5 - total % 8) & 0xff)) & 0xff;
            }
            total += 3;
            n >>= 3;
          }
          if (n != 0) total++;
        }
      }
    }
    let i = 1;
    let a = text[0];
    let w;
    for (; i < length; i++) {
      if (a == ' ') {
        push(0, 6);
        a = text[i];
        continue;
      }

      w = this.book.Encode2(a, text[i]);
      if (w != 0) {
        push(w);
        push(0, 3);
        i++;
      }
      else {
        w = this.book.Encode(a);
        if (w != 0) {
          push(w);
          push(0, 3);
        }
      }
      if (i < length) a = text[i];
    }
    if (i == length) {
      w = this.book.Encode(a);
      if (w != 0) {
        push(w);
      }
    }
    var o = new Array((total >> 3) + 1);
    Helper.ArrayCopy(bytes, o, (total >> 3) + 1);
    return o;
  }
  public Decode(data: number[], length: number) {

    if (length == 0) return "";
    var s = new Morse_bag();
    let j = 0;
    let no = 0, i;
    let load = 0;
    let zero = 0;
    while (load < length) {
      for (i = 7; i <= 7; i = (i - 1) & 0xff) {
        if (((data[load] >>> i) & 0b1) == 1) {
          if (zero >= 3) {
            if (j != 0) this.book.Decode(j, s);
            if (zero >= 6) s.text += " ";
            j = 0;
            no = 0;
            zero = 0;
          }
          else {
            no += zero & 0xff;
            zero = 0;
          }
          j |= 1 << no;
          no++;
          no &= 0xff;
        }
        else {
          zero++;
        }
      }
      load++;
    }
    if (j != 0) this.book.Decode(j, s);

    return s.text;
  }
}